﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Database;
using System.Drawing;
using System.Xml;
using System.Drawing.Imaging;
using MacomberMapSystem.Common.Serialization;
using MacomberMapSystem.Common.User_Interfaces.One_Lines;
using MacomberMapSystem.Common.Integration;
using System.Windows.Forms;
using MacomberMapSystem.Common.CIM;
using System.Threading;
using MacomberMapSystem.Common.Internals;

namespace MacomberMapSystem.Common.Exports
{
    /// <summary>
    /// This class is responsible for handling the exporing a one-line image
    /// </summary>
    public static class MM_Image_Exporter
    {
       
        /// <summary>
        /// Export a one-line image
        /// </summary>
        /// <param name="OneLine"></param>
        /// <param name="Model"></param>
        /// <param name="TargetFile"></param>
        /// <param name="OutFormat"></param>
        /// <param name="OLExport"></param>
        /// <param name="LastAssignment"></param>
        public static Bitmap ExportImage(MM_Database_OneLine OneLine, MM_Database_Model Model, String TargetFile, ImageFormat OutFormat, MM_Oneline_Export OLExport, MM_Database_Assignment LastAssignment)
        {
            if (!Model.EnsureOpened())
                return null;

            Bitmap OutImage = OneLineImage(OneLine, Model, OLExport,LastAssignment);
            OutImage.Save(TargetFile, OutFormat);
            return OutImage;
        }
        
        
        /// <summary>
        /// Produce a one-line image for a one-line XML configuration
        /// </summary>
        /// <param name="OneLine"></param>
        /// <param name="OLExport"></param>
        /// <param name="Model"></param>
        /// <param name="LastAssignment"></param>
        public static Bitmap OneLineImage(MM_Database_OneLine OneLine, MM_Database_Model Model, MM_Oneline_Export OLExport, MM_Database_Assignment LastAssignment)
        {
            if (OneLine == null)
                return null;
            else if (!Model.EnsureOpened())
                return null;


            //Determine our bounds and header text
            Point StationPoint;
            CIM_Element BaseElement;
            if (OneLine.Document.DocumentElement.HasAttribute("rdfID") && !String.IsNullOrEmpty(OneLine.Document.DocumentElement.Attributes["rdfID"].Value))
                BaseElement = Model.FromRdfID(OneLine.Document.DocumentElement.Attributes["rdfID"].Value);
            else
                BaseElement = Model.FromTEID(XmlConvert.ToUInt64(OneLine.Document.DocumentElement.Attributes["BaseElement.TEID"].Value));
            String LongName, Name;

            if (BaseElement == null)
            {
                LongName = OneLine.Document.DocumentElement.Attributes["BaseElement.LongName"].Value;
                Name = OneLine.Document.DocumentElement.Attributes["BaseElement.Name"].Value;
            }
            else
            {
                LongName = BaseElement["cim:IdentifiedObject.description"];
                Name = BaseElement.Name;
            }

                        String ExportedOn = "Exported on: " + DateTime.Now.ToShortDateString() + (LastAssignment == null ? "" : " (on " + LastAssignment.CompletedOn.ToShortDateString() + " by " + LastAssignment.CompletedBy.FullName.Substring(0,1) + LastAssignment.CompletedBy.FullName.Split(' ')[1].Substring(0,1)+")");

            String CIMFile = "CIM file: " + Model.Name;
            String OutText = (String.IsNullOrEmpty(LongName) || LongName.Equals(Name, StringComparison.CurrentCultureIgnoreCase)) ? Name : LongName + " (" + Name + ")";

            String County = "Unknown";
            if (OneLine.Document.DocumentElement.HasAttribute("BaseElement.County"))
                County = OneLine.Document.DocumentElement.Attributes["BaseElement.County"].Value;
            PointF LatLng = PointF.Empty;
            if (OneLine.Document.DocumentElement.HasAttribute("BaseElement.LatLong"))
                LatLng = MM_Serializable<PointF>.ConvertObject(OneLine.Document.DocumentElement.Attributes["BaseElement.LatLong"].Value, null);
            String Coord = String.Format("County: {0}\nLat={1:0.000}, Lng={2:0.000}", County, LatLng.Y, LatLng.X);
            int XCoord;
            Size OutSize = MaxSize(OneLine, OutText, ExportedOn, CIMFile, Coord, out StationPoint, out XCoord);

            float Reduction = 1;
            if (OutSize.Width > 32768 || (OutSize.Height+2) > 32768)
                Reduction = 32768f / (float)Math.Max(OutSize.Width, OutSize.Height);

            Bitmap OutImage = new Bitmap((int)((float)OutSize.Width*Reduction) , (int)(Reduction*(float)OutSize.Height+2), PixelFormat.Format24bppRgb);
            using (Graphics g = Graphics.FromImage(OutImage))
            {
                //First, draw our background and header

                if (MM_OneLine_Element.WhiteBackground)
                    g.Clear(Color.White);
                else
                    g.Clear(Color.Black);
                g.ScaleTransform(Reduction, Reduction);

                MM_OneLine_Element.DrawHeader(g, new Rectangle(0, 0, OutSize.Width, 37), OneLine, Model, LastAssignment);

                //If requested, draw our PUN and RAP/SPS images
                foreach (KeyValuePair<XmlElement, Rectangle> kvp in OLExport.ElementBounds)
                    if (!kvp.Key.Name.EndsWith("Descriptor"))
                    {
                        CIM_Element Elem = OLExport.ElementMapping[kvp.Key.Name == "PokePoint" ? kvp.Key.ParentNode.ParentNode as XmlElement : kvp.Key];
                        CIM_Element PUN;
                        Model.PUNElements.TryGetValue(Elem.rdfID, out PUN);
                        bool IsRap = false, IsSps = false;
                        StringBuilder Comment = new StringBuilder();
                        foreach (CIM_Element RapSPS in Elem.SPSorRAPs)
                        {
                            if (XmlConvert.ToBoolean(RapSPS["IsRAP"]))
                                IsRap = true;
                            else if (XmlConvert.ToBoolean(RapSPS["IsRAS"]))
                                IsSps = true;
                            Comment.AppendLine(RapSPS.Name + " (" + RapSPS.LongName + ")");
                        }
                        Point Offset = Point.Empty;
                        if (PUN != null && (!kvp.Key.HasAttribute("IsVisible") || bool.Parse(kvp.Key.Attributes["IsVisible"].Value)))
                        {
                            g.FillRectangle(Brushes.DarkSlateGray, new Rectangle(Offset.X + kvp.Value.Left - 10, Offset.Y + kvp.Value.Top - 10, kvp.Value.Width + 20, kvp.Value.Height + 20));

                            //If we have a node included in a PUN, let's trace everything out from there
                            if (kvp.Key.Name == "Node")
                                foreach (XmlElement xTargetElem in kvp.Key.ChildNodes)
                                    if (xTargetElem.Name != "Descriptor" && xTargetElem.Name != "SecondaryDescriptor")
                                    {
                                        XmlElement TargetElem = kvp.Key.OwnerDocument.SelectSingleNode("//*[@BaseElement.TEID='" + xTargetElem.Attributes["TEID"].Value + "']") as XmlElement;
                                        Rectangle TargetRect = kvp.Value;
                                        Point Center1, Center2;
                                        if (Model.PUNElements.ContainsKey(OLExport.ElementMapping[TargetElem].rdfID))
                                        {
                                            foreach (XmlElement xNodeCh in xTargetElem.ChildNodes)
                                                if (xNodeCh.Name == "PokePoint" || xNodeCh.Name == "PricingVector")
                                                {
                                                    Rectangle Bounds2 = MM_Serializable<Rectangle>.ConvertObject(xNodeCh.Attributes["Bounds"].Value, null);
                                                    MM_DDL_Exporter.GetStraightLine(TargetRect, Bounds2, out Center1, out Center2, false, false);
                                                    g.FillRectangle(Brushes.DarkSlateGray, Offset.X + Center1.X - 10, Offset.Y + Center1.Y - 10, Offset.X  + Center2.X - Center1.X + 20, Offset.Y  + Center2.Y - Center1.Y + 20);
                                                    TargetRect = Bounds2;
                                                }
                                            MM_DDL_Exporter.GetStraightLine(TargetRect, MM_Serializable<Rectangle>.ConvertObject(TargetElem.Attributes["Bounds"].Value, null), out Center1, out Center2, false, false);
                                            g.FillRectangle(Brushes.DarkSlateGray, Offset.X + Center1.X - 10, Offset.Y  + Center1.Y - 10, Offset.X + Center2.X - Center1.X + 20, Offset.Y + Center2.Y - Center1.Y + 20);
                                        }
                                    }
                        }
                        if (kvp.Key.HasAttribute("IsVisible") && !bool.Parse(kvp.Key.Attributes["IsVisible"].Value))
                        { }
                        else if (IsRap && IsSps)
                        {
                            g.FillRectangle(Brushes.DimGray, new Rectangle(Offset.X + kvp.Value.Left - 5, Offset.Y + kvp.Value.Top - 5, (kvp.Value.Height + 10) / 2, kvp.Value.Width + 10));
                            g.FillRectangle(Brushes.DarkOliveGreen, new Rectangle(Offset.X + kvp.Value.Left - 5, Offset.Y + kvp.Value.Top + ((kvp.Value.Height + 10) / 2) - 5, (kvp.Value.Height + 10) / 2, kvp.Value.Width + 10));
                        }
                        else if (IsRap)
                            g.FillRectangle(Brushes.DimGray, new Rectangle(Offset.X + kvp.Value.Left - 5, Offset.Y + kvp.Value.Top - 5, kvp.Value.Width + 10, kvp.Value.Height + 10));
                        else if (IsSps)
                            g.FillRectangle(Brushes.DarkOliveGreen, new Rectangle(Offset.X + kvp.Value.Left - 5, Offset.Y + kvp.Value.Top - 5, kvp.Value.Width + 10, kvp.Value.Height + 10));
                    }
            

                //Now, start by writing out our nodes
                foreach (XmlElement xNode in OneLine.Document.SelectNodes("One_Line/Nodes/Node"))
                    WriteNode(xNode, Model, g, MM_OneLine_Element.WhiteBackground ? Brushes.White :  Brushes.Black, BaseElement, OLExport);

                //Now, write out our elements                
                foreach (XmlElement xElem in OneLine.Document.SelectNodes("One_Line/Elements/*"))
                    WriteElement(xElem, g, MM_OneLine_Element.WhiteBackground ? Brushes.White : Brushes.Black, Model, BaseElement);

                foreach (XmlElement xUnlinked in OneLine.Document.SelectNodes("One_Line/Unlinked_Elements/*"))
                {
                    //Determine our attributes for drawing, and clear the background as needed
                    Font DrawFont = (xUnlinked.HasAttribute("Font")) ? MM_Serializable<Font>.ConvertObject(xUnlinked.Attributes["Font"].Value, null) : SystemFonts.DefaultFont;
                    Color BackColor = xUnlinked.HasAttribute("BackColor") ? MM_Serializable<Color>.ConvertObject(xUnlinked.Attributes["BackColor"].Value, null) : Color.Transparent;
                    Color ForeColor = xUnlinked.HasAttribute("ForeColor") ? MM_Serializable<Color>.ConvertObject(xUnlinked.Attributes["ForeColor"].Value, null) : Color.White;
                    Rectangle Bounds = MM_Serializable<Rectangle>.ConvertObject(xUnlinked.Attributes["Bounds"].Value, null);
                    if (BackColor != Color.Transparent)
                        using (SolidBrush BackBrush = new SolidBrush(BackColor))
                            g.FillRectangle(BackBrush, Bounds);


                    if (xUnlinked.Name == "Label" && xUnlinked.HasAttribute("Text"))
                        using (SolidBrush ForeBrush = new SolidBrush(ForeColor))
                            g.DrawString(xUnlinked.Attributes["Text"].Value, DrawFont, ForeBrush, Bounds.Location);
                    else if (xUnlinked.Name == "Rectangle")
                        using (Pen DrawPen = new Pen(ForeColor))
                            g.DrawRectangle(DrawPen, Bounds);
                    else if (xUnlinked.Name == "Circle")
                        using (Pen DrawPen = new Pen(ForeColor))
                            g.DrawEllipse(DrawPen, Bounds);
                    else if (xUnlinked.Name == "Image")
                        using (System.IO.MemoryStream mS = new System.IO.MemoryStream(System.Convert.FromBase64String(xUnlinked.Attributes["Image"].Value)))
                        using (Image DrawImage = Image.FromStream(mS))
                            g.DrawImage(DrawImage, Bounds);
                }
            }
            return OutImage;
        }

        /// <summary>
        /// Write out a node
        /// </summary>
        /// <param name="xNode"></param>
        /// <param name="g"></param>
        /// <param name="Background"></param>
        /// <param name="BaseElement"></param>
        /// <param name="Model"></param>
        /// <param name="OLExport"></param>
        private static void WriteNode(XmlElement xNode, MM_Database_Model Model, Graphics g, Brush Background, CIM_Element BaseElement, MM_Oneline_Export OLExport)
        {

            Rectangle Bounds = MM_Serializable<Rectangle>.ConvertObject(xNode.Attributes["Bounds"].Value, null);
            Point Center1, Center2;
            MM_OneLine_Element.enumOrientations Orientation = MM_Serializable<MM_OneLine_Element.enumOrientations>.ConvertObject(xNode.Attributes["Orientation"].Value, null);
            MM_KVLevel KVLevel = Data_Manager.LocateKVLevel(xNode.Attributes["BaseElement.KVLevel"].Value);
            bool IsJumper = xNode.HasAttribute("IsJumper") ? XmlConvert.ToBoolean(xNode.Attributes["IsJumper"].Value) : false;
            MM_OneLine_Element.DrawPokePoint(g, Bounds, Background, Orientation, KVLevel, IsJumper);


            foreach (XmlElement xTargetElem in xNode.ChildNodes)
                if (xTargetElem.Name == "Descriptor" || xTargetElem.Name == "SecondaryDescriptor")
                    WriteDescriptor(xTargetElem, Bounds, g, Background, BaseElement, Model);
                else
                {
                    XmlElement TargetElem = xNode.OwnerDocument.SelectSingleNode("//*[@BaseElement.TEID='" + xTargetElem.Attributes["TEID"].Value + "']") as XmlElement;
                    Rectangle TargetRect = Bounds; // MM_Serializable<Rectangle>.ConvertObject(TargetElem.Attributes["Bounds"].Value, null);

                    foreach (XmlElement xNodeCh in xTargetElem.ChildNodes)
                        if (xNodeCh.Name == "Descriptor" || xNodeCh.Name == "SecondaryDescriptor")  
                            WriteDescriptor(xNodeCh, Bounds, g, Background, BaseElement, Model);
                        else if (xNodeCh.Name == "PokePoint" || xNodeCh.Name == "PricingVector")
                        {
                            Rectangle Bounds2 = MM_Serializable<Rectangle>.ConvertObject(xNodeCh.Attributes["Bounds"].Value, null);
                            MM_DDL_Exporter.GetStraightLine(TargetRect, Bounds2, out Center1, out Center2, false, false);

                            using (Pen DrawPen = new Pen(KVLevel.Energized.ForeColor, MM_OneLine_Element.ThicknessOverride))
                                g.DrawLine(DrawPen, Center1, Center2);

                            Orientation = xNodeCh.HasAttribute("Orientation") ? MM_Serializable<MM_OneLine_Element.enumOrientations>.ConvertObject(xNodeCh.Attributes["Orientation"].Value, null) : MM_OneLine_Element.enumOrientations.Vertical;
                            if (xNodeCh.Name == "PricingVector")
                                MM_OneLine_Element.DrawPricingVector(g, Bounds2, Background, Orientation, KVLevel, XmlConvert.ToBoolean(xNodeCh.Attributes["IsPositive"].Value));
                            else
                                MM_OneLine_Element.DrawPokePoint(g, Bounds2, Background, Orientation, KVLevel, xNodeCh.HasAttribute("IsJumper") && XmlConvert.ToBoolean(xNodeCh.Attributes["IsJumper"].Value));
                            WriteDescriptor(xNodeCh["Descriptor"], Bounds, g, Background, BaseElement, Model);
                            WriteDescriptor(xNodeCh["SecondaryDescriptor"], Bounds, g, Background, BaseElement, Model);
                            TargetRect = Bounds2;
                        }
                    MM_DDL_Exporter.GetStraightLine(TargetRect, MM_Serializable<Rectangle>.ConvertObject(TargetElem.Attributes["Bounds"].Value, null), out Center1, out Center2, false, false);


                    //Determine if our target element is assosicate with a PUN
                    using (Pen DrawPen = new Pen(KVLevel.Energized.ForeColor, MM_OneLine_Element.ThicknessOverride))
                        g.DrawLine(DrawPen, Center1, Center2);
                }




        }

        /// <summary>
        /// Write out an element
        /// </summary>
        /// <param name="xElem"></param>
        /// <param name="g"></param>
        /// <param name="Background"></param>
        /// <param name="Model"></param>
        /// <param name="BaseElement"></param>        
        private static void WriteElement(XmlElement xElem, Graphics g, Brush Background, MM_Database_Model Model, CIM_Element BaseElement)
        {
            Rectangle Bounds = MM_Serializable<Rectangle>.ConvertObject(xElem.Attributes["Bounds"].Value, Model);
            MM_OneLine_Element.enumOrientations Orientation = MM_Serializable<MM_OneLine_Element.enumOrientations>.ConvertObject(xElem.Attributes["Orientation"].Value, Model);
            if (xElem.Name == "Transformer")
                foreach (XmlElement xWinding in xElem.SelectNodes("Winding"))
                {

                    if (xWinding.HasAttribute("BaseElement.KVLevel"))
                    {
                        Rectangle WindingBounds = MM_Serializable<Rectangle>.ConvertObject(xWinding.Attributes["Bounds"].Value, Model);
                        WindingBounds.X += Bounds.Left;
                        WindingBounds.Y += Bounds.Top;
                        MM_OneLine_Element.enumOrientations WindingOrientation = MM_Serializable<MM_OneLine_Element.enumOrientations>.ConvertObject(xWinding.Attributes["Orientation"].Value, Model);
                        CIM_Element Elem = Model.FromRdfID(xWinding.Attributes["rdfID"].Value);
                        MM_KVLevel WindingKV = Data_Manager.LocateKVLevel(Elem.VoltageLevel);
                        bool Visible = xWinding.HasAttribute("Visible") ? XmlConvert.ToBoolean(xWinding.Attributes["Visible"].Value) : true;
                        bool IsPhaseShifter = xWinding.ParentNode.Attributes["BaseElement.PhaseShifter"] == null ? false : XmlConvert.ToBoolean(xWinding.ParentNode.Attributes["BaseElement.PhaseShifter"].Value);
                        bool IsPrimary;
                        if (xWinding.HasAttribute("BaseElement.IsPrimary"))
                            IsPrimary = XmlConvert.ToBoolean(xWinding.Attributes["BaseElement.IsPrimary"].Value);
                        else
                            IsPrimary = Elem.Attributes["cim:TransformerWinding.windingType"].EndsWith("primary");
                        MM_OneLine_Element.DrawTransformerWinding(g, WindingBounds, Background, WindingOrientation, WindingKV, Visible, IsPhaseShifter && IsPrimary);
                    }
                }
            else
            {
                MM_KVLevel KVLevel = Data_Manager.LocateKVLevel(xElem.Attributes["BaseElement.KVLevel"].Value);

                if (xElem.Name == "Breaker")
                    MM_OneLine_Element.DrawBreaker(g, Bounds, Background, Orientation, KVLevel, MM_Serializable<CheckState>.ConvertObject(xElem.Attributes["Opened"].Value, Model));
                else if (xElem.Name == "Switch")
                    MM_OneLine_Element.DrawSwitch(g, Bounds, Background, Orientation, KVLevel, MM_Serializable<CheckState>.ConvertObject(xElem.Attributes["Opened"].Value, Model));
                else if (xElem.Name == "Unit")
                    MM_OneLine_Element.DrawUnit(g, Bounds, Background, Orientation, KVLevel, .5f);
                else if (xElem.Name == "Line")
                    MM_OneLine_Element.DrawLine(g, Bounds, Background, Orientation, KVLevel);
                else if (xElem.Name == "Reactor")
                    MM_OneLine_Element.DrawReactor(g, Bounds, Background, Orientation, KVLevel,true);
                else if (xElem.Name == "Load")
                    MM_OneLine_Element.DrawLoad(g, Bounds, Background, Orientation, KVLevel);
                else if (xElem.Name == "LAAR")
                    MM_OneLine_Element.DrawLaaR(g, Bounds, Background, Orientation, KVLevel);
                else if (xElem.Name == "EndCap")
                    MM_OneLine_Element.DrawEndCap(g, Bounds, Background, Orientation, KVLevel);
                else if (xElem.Name == "Capacitor")
                    MM_OneLine_Element.DrawCapacitor(g, Bounds, Background, Orientation, KVLevel,true);
                else if (xElem.Name == "StaticVarCompensator")
                    MM_OneLine_Element.DrawStaticVarCompensator(g, Bounds, Background, Orientation, KVLevel);
                else if (xElem.Name == "ResistorReactor")
                    MM_OneLine_Element.DrawResistorReactor(g, Bounds, Background, Orientation, KVLevel);
                else if (xElem.Name == "ResistorCapacitor")
                    MM_OneLine_Element.DrawResistorCapacitor(g, Bounds, Background, Orientation, KVLevel);
                else if (xElem.Name == "Resistor")
                    MM_OneLine_Element.DrawResistor(g, Bounds, Background, Orientation, KVLevel,true);
                else

                    throw new InvalidOperationException("Can't draw " + xElem.Name);
            }
            WriteDescriptor(xElem["Descriptor"], Bounds, g, Background, BaseElement, Model);
            WriteDescriptor(xElem["SecondaryDescriptor"], Bounds, g, Background, BaseElement, Model);
        }

        /// <summary>
        /// Write out the descriptor for an element
        /// </summary>
        /// <param name="xDesc"></param>
        /// <param name="ParentBounds"></param>
        /// <param name="g"></param>
        /// <param name="Background"></param>
        /// <param name="BaseElement"></param>
        /// <param name="Model"></param>
        private static void WriteDescriptor(XmlElement xDesc, Rectangle ParentBounds, Graphics g, Brush Background, CIM_Element BaseElement, MM_Database_Model Model)
        {
            if (xDesc == null)
                return;

            CIM_Element Elem;
            if ((xDesc.ParentNode as XmlElement).HasAttribute("rdfID"))
                Elem = Model.FromRdfID(xDesc.ParentNode.Attributes["rdfID"].Value);
            else
                Elem = Model.FromTEID(XmlConvert.ToUInt64(xDesc.ParentNode.Attributes["BaseElement.TEID"].Value));

            Rectangle DescBounds = MM_Serializable<Rectangle>.ConvertObject(xDesc.Attributes["Bounds"].Value, Model);

            if ((xDesc.HasAttribute("DescriptorArrow") && XmlConvert.ToBoolean(xDesc.Attributes["DescriptorArrow"].Value)) || (xDesc.ParentNode.Attributes["DescriptorArrow"] != null && XmlConvert.ToBoolean(xDesc.ParentNode.Attributes["DescriptorArrow"].Value)))
                MM_OneLine_Element.DrawArrow(g, DescBounds, ParentBounds);
            MM_OneLine_Element.DrawDescriptor(g, Elem, BaseElement, xDesc, DescBounds, Background, Model);
        }

        /// <summary>
        /// Determine the maximum size of the one-line
        /// </summary>
        /// <param name="OneLine"></param>
        /// <param name="StationPoint"></param>
        /// <param name="CIMFile"></param>
        /// <param name="ExportedOn"></param>
        /// <param name="OutText"></param>
        /// <param name="XCoord"></param>
        /// <param name="Coord"></param>
        /// <returns></returns>        
        public static Size MaxSize(MM_Database_OneLine OneLine, String OutText, String ExportedOn, String CIMFile, String Coord, out Point StationPoint, out int XCoord)
        {
            //First, come up with our initial size
            Size OutSize = new Size(0, 0);
            StationPoint = new Point(0, 0);
            foreach (XmlNode xNode in OneLine.Document.SelectNodes("//@Bounds"))
            {
                Rectangle Parse = MM_Serializable<Rectangle>.ConvertObject(xNode.Value, null);
                OutSize.Width = Math.Max(OutSize.Width, Parse.Right);
                OutSize.Height = Math.Max(OutSize.Height, Parse.Bottom);
            }

            //Now, determine the position of our header name

            using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
            {
                int LeftStart;
                using (Font ArialFont = new Font("Arial", 9))
                {
                    XCoord = OutSize.Width - Math.Max(g.MeasureString(ExportedOn, ArialFont).ToSize().Width, g.MeasureString(CIMFile, ArialFont).ToSize().Width);
                    LeftStart = g.MeasureString(Coord, ArialFont).ToSize().Width + 4;
                }

                int StationFontSize = 20;
                using (Font ArialFont = new Font("Arial", StationFontSize * 3 / 4, FontStyle.Underline))
                {
                    Size TitleSize = Size.Ceiling(g.MeasureString(OutText, ArialFont));

                    int TitleWidth = XCoord - 141 - LeftStart;
                    StationPoint = new Point(((TitleWidth - TitleSize.Width) / 2) + LeftStart, 8);

                    while (TitleSize.Width > TitleWidth || TitleWidth < LeftStart)
                    {
                        XCoord += 10;
                        OutSize.Width += 10;
                        TitleWidth = XCoord - 141 - LeftStart;
                        StationPoint = new Point(((TitleWidth - TitleSize.Width) / 2) + LeftStart, 8);
                    }
                    StationPoint.X += LeftStart;
                }
            }

            return OutSize;
        }
    }
}